Initial import of Cobalt 2.8885 2016-07-27
diff --git a/src/.clang-format b/src/.clang-format new file mode 100644 index 0000000..d5c7b08 --- /dev/null +++ b/src/.clang-format
@@ -0,0 +1,3 @@ +# Defines the Chromium style for automatic reformatting. +# http://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: Chromium
diff --git a/src/AUTHORS b/src/AUTHORS new file mode 100644 index 0000000..5a7ed82 --- /dev/null +++ b/src/AUTHORS
@@ -0,0 +1,12 @@ +# Names should be added to this file with this pattern: +# +# For individuals: +# Name <email address> +# +# For organizations: +# Organization <fnmatch pattern> +# +# See python fnmatch module documentation for more information. + +The Chromium Authors <*@chromium.org> +Google Inc. <*@google.com>
diff --git a/src/LICENSE b/src/LICENSE new file mode 100644 index 0000000..3d0f7d3 --- /dev/null +++ b/src/LICENSE
@@ -0,0 +1,27 @@ +// Copyright (c) 2013 The Chromium Authors. 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/README.cobalt.txt b/src/README.cobalt.txt new file mode 100644 index 0000000..bc2d9c7 --- /dev/null +++ b/src/README.cobalt.txt
@@ -0,0 +1,2 @@ +This is a fork of the chromium repository at http://git.chromium.org/git/chromium.git +
diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..2d2efba --- /dev/null +++ b/src/README.md
@@ -0,0 +1,247 @@ +# Cobalt + +## Overview + +Cobalt is a lightweight application container (i.e. an application runtime, like +a JVM or the Flash Player) that is compatible with a subset of the W3C HTML5 +specifications. If you author a single-page web application (SPA) that complies +with the Cobalt Subset of W3C standards, it will run as well as possible on all +the devices that Cobalt supports. + + +## Motivation + +The Cobalt Authors originally maintained a port of Chromium called H5VCC, the +HTML5 Video Container for Consoles, ported to each of the major game consoles, +designed to run our HTML5-based video browse and play application. This took a +long time to port to each platform, consisted of 9 million lines of C++ code +(before we touched it), was dangerous to modify without unintended consequences, +and was thoroughly designed for a resource-rich, multi-process environment +(e.g. a desktop, laptop, or modern smartphone). + +After wrestling with this for several years, we imagined an environment that was +not designed for traditional scrolling web content, but was intended to be a +runtime environment for rich client applications built with the same +technologies -- HTML, CSS, JavaScript -- and designed from the ground-up to run +on constrained, embedded, Living Room Consumer Electronics (CE) devices, such as +Game Consoles, Set-Top Boxes (e.g. Cable, Satellite), OTT devices (e.g. Roku, +Apple TV, Chromecast, Fire TV), Blu-ray Disc Players, and Smart TVs. + +These constraints (not intended to be a canonical list) make this device +spectrum vastly different from the desktop computer environment targeted by +Chromium, FireFox, and IE: + + * **Limited Memory.** All except the very latest, expensive CE devices have a + very small amount of memory available for applications. This usually is + somewhere in the ballpark of 200MB-500MB, including graphics and media + memory, as opposed to multiple gigabytes of CPU memory (and more gigabytes + of GPU memory) in modern desktop and laptop computers, and mobile devices. + * **Slow CPUs.** Most CE devices have much slower CPUs than what is available + on even a budget desktop computer. Minor performance concerns can be greatly + exaggerated, which seriously affects priorities. + * **Fewer cores.** CE System-on-a-Chip (SoC) processors often do not have as + many processor cores as we are used to in modern computers. Many deployed + devices still only have a single core. + * **Sometimes No GPU.** Not all CE devices have a monster GPU to throw shaders + at to offload CPU work. A different strategy is required to maximize + leverage of an accelerated blitter, which is all some older devices + have. Some newer CE devices have a GPU, but it's not nearly as powerful as + what one would even see on a laptop. + * **Sometimes No JIT.** Many CE devices are dealing with "High-Value Content," + and, as such, are very sensitive to security concerns. Ensuring that + writable pages are not executable is a strong security protocol that can + prevent a wide spectrum of attacks. But, as a side effect, this also means + no ability to JIT. + * **Heterogenous Development Environments.** This is slowly evening out, but + all CE devices run on custom hardware, often with proprietary methods of + building, packaging, deploying, and running programs. Almost all CE devices + have ARM or MIPS processors instead of the more familiar x86. Sometimes the + toolchain doesn't support contemporary C++11 features. Sometimes the OS + isn't POSIX, or it tries to be, but it is only partially implemented. + Sometimes the program entry point is in another language or architecture + that requires a "trampoline" over to native binary code. + * **No navigation.** The point of a Single-Page Application is that you don't + go through the HTTP page dance every time you switch screens. It's slow, and + provides poor user feedback, not to mention a jarring transition. Instead, + one loads data from an XMLHttpRequest (XHR), and then updates one's DOM to + reflect the new data. AJAX! Web 2.0!! + * **No scrolling.** Well, full-screen, 10-foot UI SPA apps might scroll, but + not like traditional web pages, with scroll bars and a mouse + wheel. Scrolling is generally built into the app very carefully, with + support for a Directional Pad and a focus cursor. + + +## Architecture + +The Cobalt Authors forked H5VCC, removed most of the Chromium code -- in +particular WebCore and the Chrome Renderer and Compositor -- and built up from +scratch an implementation of a simplified subset of HTML, the CSS Box Model for +layout, and the Web APIs that were really needed to build a full-screen SPA +browse and play application. + +The Cobalt technology stack has these major components, roughly in a high-level +application to a low-level platform order: + + * **Web Implementation** - This is where the W3C standards are implemented, + ultimately producing an annotated DOM tree that can be passed into the + Layout Engine to produce a Render Tree. + * **JavaScript Engine** - We have, perhaps surprisingly, *not* written our own + JavaScript Engine from scratch. Because of the JITing constraint, we have to + be flexible with which engine(s) we work with. We have a bindings layer that + interfaces with the JS Engine so that application script can interface with + natively-backed objects (like DOM elements). + * **Layout Engine** - The Layout Engine takes an annotated DOM Document + produced by the Web Implementation and JavaScript Engine working together, + and calculates a tree of rendering commands to send to the renderer (i.e. a + Render Tree). It caches intermediate layout artifacts so that subsequent + incremental layouts can be sped up. + * **Renderer/Skia** - The Renderer walks a Render Tree produced by the Layout + Engine, rasterizes it using the third-party graphics library Skia, and swaps + it to the front buffer. There are two major paths here, one using Hardware + Skia on OpenGL ES 2.0, and one using Software Skia combined with the + hardware-accelerated Starboard Blitter. Note that the renderer runs in a + different thread from the Layout Engine, and can interpolate animations that + do not require re-layout. This decouples rendering from Layout and + JavaScript, allowing for smooth, consistent animations on platforms with a + variety of capabilities. + * **Net / Media** - These are Chromium's Network and Media engines. We are + using them directly, as they don't cause any particular problems with the + extra constraints listed above. + * **Base** - This is Chromium's "Base" library, which contains a wide variety + of useful things used throughout Cobalt, Net, and Media. Cobalt uses a + combination of standard C++ containers (e.g. vector, string) and Base as the + foundation library for all of its code. + * **Other Third-party Libraries** - Most of these are venerable, straight-C, + open-source libraries that are commonly included in other open-source + software. Mostly format decoders and parsers (e.g. libpng, libxml2, + zlib). We fork these from Chromium, as we want them to be the most + battle-tested versions of these libraries. + * **Starboard/Glimp/ANGLE** - **Starboard** is the Cobalt porting + interface. One major difference between Cobalt and Chromium is that we have + created a hard straight-C porting layer, and ported ALL of the compiled + code, including Base and all third-party libraries, to use it instead of + directly using POSIX standard libraries, which are not consistent, even on + modern systems (see Android, Windows, MacOS X, and iOS). Additionally, + Starboard includes APIs that haven't been effectively standardized across + platforms, such as display Window creation, Input events, and Media + playback. **Glimp** is an OpenGL ES 2.0 implementation framework, built by + the Cobalt team directly on Starboard, designed to adapt proprietary 3D APIs + to GLES2. **ANGLE** Is a third-party library that adapts DirectX to GLES2, + similar to Glimp, but only for DirectX. + +Cobalt is like a flaky layered pastry - perhaps Baklava. It shouldn't be too +difficult to rip the Web Implementation and Layout off the top, and just use the +Renderer, or even to just use Base + Starboard + GLES2 as the basis of a new +project. + + +## The Cobalt Subset + +> Oh, we got both kinds of HTML tags,\ +> we got `<span>` and `<div>`! + +See the [Cobalt Subset specification](TODO) for more details on which tags, +properties, and Web APIs are supported in Cobalt. + +*More to come.* + + +## Interesting Source Locations + +All source locations are specified relative to `src/` (this directory). + + * `base/` - Chromium's Base library. Contains common utilities, and a light + platform abstraction, which has been superceded in Cobalt by Starboard. + * `net/` - Chromium's Network library. Contains enough infrastructure to + support the network needs of an HTTP User-Agent (like Chromium or Cobalt), + an HTTP server, a DIAL server, and several abstractions for networking + primitives. Also contains SPDY and QUIC implementations. + * `media/` - Chromium's Media library. Contains all the code that parses, + processes, and manages buffers of video and audio data. Media decoding is + passed off to decoding hardware, wherever possible. + * `cobalt/` - The home of all Cobalt application code. This includes the Web + Implementation, Layout Engine, Renderer, and some other Cobalt-specific + features. + * `cobalt/build/` - The core build generation system, `gyp_cobalt`, and + configurations for supported platforms. (NOTE: This should eventually be + mostly moved into `starboard/`.) + * `starboard/` - Cobalt's porting layer. Please see Starboard's + [`README.md`](starboard/README.md) for more detailed information about + porting Starboard (and Cobalt) to a new platform. + * `third_party/` - Where all of Cobalt's third-party dependencies live. We + don't mean to be perjorative, we love our third-party libraries! This + location is dictated by Google OSS release management rules... + * `third_party/starboard/` - The location for third-party ports. This + directory will be scanned automatically by gyp_cobalt for available + Starboard ports. + + +## Building and Running the Code + +Here's a quick and dirty guide to get to build the code on Linux. + + 1. Install the provided `depot_tools` archive into your favorite directory. It + has been slightly modified from Chromium's `depot_tools`. + 2. Add that directory to the end of your `$PATH`. + 3. Ensure you have these packages installed: `sudo apt-get install + libgles2-mesa-dev libpulse-dev libavformat-dev libavresample-dev + libasound2-dev libxrender-dev libxcomposite-dev` + 4. Ensure you have the standard C++ header files installed + (e.g. `libstdc++-4.8-dev`). + 5. Remove bison-3 and install bison-2.7, or just make sure that bison-2.7 is + before bison-3 on your `$PATH`. (NOTE: We plan on moving to bison-3 in the + future.) + + $ sudo apt-get remove bison + $ sudo apt-get install m4 + $ wget http://ftp.gnu.org/gnu/bison/bison-2.7.1.tar.gz + $ tar zxf bison-2.7.1.tar.gz + $ cd bison-2.7.1 + $ sh configure && make && sudo make install + $ which bison + /usr/local/bin/bison + $ bison --version + bison (GNU Bison) 2.7.12-4996 + + 6. (From this directory) run GYP: + + cobalt/build/gyp_cobalt -C debug linux-x64x11 + + 7. If you get a "clang not found" error, add the path to Cobalt's clang to + your `$PATH` and rerun `gyp_cobalt` as above. For example: + `/path/to/cobalt/src/third_party/llvm-build/Release+Asserts/bin` + 8. Run Ninja: + + ninja -C out/linux-x64x11_debug cobalt + + 9. Run Cobalt: + + out/linux-x64x11_debug/cobalt [--url=<url>] + + * If you want to use `http` instead of `https`, you must pass the + `--allow_http` flag to the Cobalt command-line. + * If you want to connect to an `https` host that doesn't have a + globally-validatable certificate, you must pass the + `--ignore_certificate_errors` flag to the Cobalt command-line. + * See `cobalt/browser/switches.cc` for more command-line options. + + +## Build Types + +Cobalt has four build optimization levels, going from the slowest, least +optimized, with the most debug information at the top (debug) to the fastest, +most optimized, and with the least debug information at the bottom (gold): + + | Type | Optimizations | Logging | Asserts | Debug Info | Console | + | :---- | :------------ | :------ | :------ | :--------- | :------- | + | debug | None | Full | Full | Full | Enabled | + | devel | Full | Full | Full | Full | Enabled | + | qa | Full | Limited | None | None | Enabled | + | gold | Full | None | None | None | Disabled | + +When building for release, you should always use a gold build for the final +product. + + $ cobalt/build/gyp_cobalt -C gold linux-x64x11 + $ ninja -C out/linux-x64x11_gold cobalt + $ out/linux-x64x11_gold/cobalt
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); <